#region Using References

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using Beaker.OpenCube.Anvil;
using Beaker.OpenCube.Game;
using Beaker.OpenCube.Mapping;
using Beaker.OpenCube.Nbt;
using Beaker.Services.FileSystem;

#endregion

namespace Beaker.OpenCube.Dynamic
{
	/// <summary>
	/// 
	/// </summary>
	public sealed class DynamicDimension
	{
		private readonly IFolder _dimensionFolder;

		/// <summary>
		/// Initializes a new instance of the <see cref="DynamicDimension"/> class.
		/// </summary>
		/// <param name="dimensionFolder">The folder path of the dimension.</param>
		/// <exception cref="System.ArgumentException">The service locator did not provided an implementation for IIOService;serviceLocator</exception>
		internal DynamicDimension( IFolder dimensionFolder )
		{
			Contract.Requires<ArgumentNullException>( dimensionFolder != null );
			_dimensionFolder = dimensionFolder;
		}

		[ContractInvariantMethod]
		private void ContractInvariant()
		{
			Contract.Invariant( _dimensionFolder != null );
		}


		/// <summary>
		/// Gets a value indicating whether this dimension has chunks.
		/// </summary>
		/// <value>
		/// <c>true</c> if this dimension has chunks; otherwise, <c>false</c>.
		/// </value>
		public bool HasChunks
		{
			get { return true; }
		}

		private readonly WeakDictionary<Coordinates2<int>, Chunk> _chunkCache =
			new WeakDictionary<Coordinates2<int>, Chunk>();

		/// <summary>
		/// Gets the chunk at the given coordinates.
		/// </summary>
		/// <param name="x">The x coordinate of the chunk.</param>
		/// <param name="z">The z coordinate of the chunk.</param>
		/// <returns>The chunk at the given coordinates, or <c>null</c> when there is no chunk generated at the given coordinates.</returns>
		public Chunk GetChunk( int x, int z )
		{
			// check cache
			Chunk chunk;
			var coordinates = new Coordinates2<int>( x, z );
			if( _chunkCache.TryGetValue( coordinates, out chunk ) )
				return chunk;

			// determine file of chunk
			string fileName = RegionReader.GetChunkFileName( x, z );
			IFile file = _dimensionFolder.GetFile( fileName );

			// when the file does not exists, return null (i.e., chunk is not generated)
			if( file == null )
				return null;

			using( Stream stream = file.OpenRead() )
			{
				NbtDictionary chunkDataDict = RegionReader.ReadChunk( stream, x, z );
				if( chunkDataDict != null && chunkDataDict.ContainsKey( "Level" ) )
				{
					var chunkLevelDataDict = chunkDataDict["Level"] as IDictionary<string, object>;
					if( chunkLevelDataDict != null )
					{
						try
						{
							var mapper = new Mapper<Chunk>();
							chunk = mapper.MapDictionary( chunkLevelDataDict );

							_chunkCache.Add( coordinates, chunk );
							return chunk;
						}
						catch( MappingException )
						{
							// return no chunk, when it fails to load correctly
							return null;
						}
					}
				}
			}

			return null;
		}
	}
}